﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace BushakwiaRLF
{
    /// <summary>
    /// Object responsible for RLF calculations on a single input image.
    /// </summary>
    public class RLFClient
    {
        #region Native functions

        [DllImport("RunLengthFeatures.dll", EntryPoint = @"?calculateForDirection@@YAXHHH@Z", CallingConvention = CallingConvention.Cdecl)]
        private static extern void calculateForDirection(int direction, int windowSizeX, int windowSizeY);

        [DllImport("RunLengthFeatures.dll", EntryPoint = @"?destroy@@YAXXZ", CallingConvention = CallingConvention.Cdecl)]
        private static extern void destroy();

        [DllImport("RunLengthFeatures.dll", EntryPoint = @"?getRLFImage@@YAXPAHH@Z", CallingConvention = CallingConvention.Cdecl)]
        private static extern void getRLFImage(int[] rlfImage, int imageNumber);

        [DllImport("RunLengthFeatures.dll", EntryPoint = @"?initialize@@YAXPAHHHHH@Z", CallingConvention = CallingConvention.Cdecl)]
        private static extern void initialize(int[] inputPixels, int pixelsLength, int imageWidth, int sampleSizeX, int sampleSizeY);

        #endregion

        #region Fields and properties

        private Dictionary<Tuple<Directions, Metrics>, Bitmap> rlfImages;

        private int windowSize;
        private int sampleSize;
        private int imageWidth;
        private int imagePixelCount;

        private int outputImageWidth;
        private int outputImageHeight;
        private int outputPixelCount;

        private int[] sourcePixels;

        public Dictionary<Tuple<Directions, Metrics>, Bitmap> RlfImages
        {
            get { return rlfImages; }
        }

        #endregion

        #region Public methods

        /// <summary>
        /// Initializes the object.
        /// </summary>
        public RLFClient(Bitmap sourceImage, int windowSize, int sampleSize)
        {
            this.rlfImages = new Dictionary<Tuple<Directions, Metrics>, Bitmap>();
            this.windowSize = windowSize;
            this.sampleSize = sampleSize;
            this.imagePixelCount = sourceImage.Height * sourceImage.Width;
            this.imageWidth = sourceImage.Width;

            // calculating dimensions of output images
            this.outputImageWidth = sourceImage.Width / sampleSize;
            if (sourceImage.Width % sampleSize != 0)	// one additional pixel of size needed
                this.outputImageWidth++;

            this.outputImageHeight = sourceImage.Height / sampleSize;
            if (sourceImage.Height % sampleSize != 0)
                this.outputImageHeight++;

            this.outputPixelCount = this.outputImageWidth * this.outputImageHeight;

            this.sourcePixels = BitmapToPixels(sourceImage);

            RLFClient.initialize(this.sourcePixels, this.imagePixelCount, this.imageWidth, sampleSize, sampleSize);
        }

        /// <summary>
        /// This is time consuming. It should be starter on another thread.
        /// </summary>
        public void CalculateOutputImages()
        {
            // calculate all 15 images
            calculateForDirection(0, this.windowSize, this.windowSize);
            calculateForDirection(1, this.windowSize, this.windowSize);
            calculateForDirection(2, this.windowSize, this.windowSize);

            // get all 15 images
            int[] pixels = new int[this.outputPixelCount];
            // NOTE: it is important for enums to be defined in correct order
            int i = 0;
            foreach (Directions dir in Enum.GetValues(typeof(Directions)))
            {
                foreach (Metrics met in Enum.GetValues(typeof(Metrics)))
                {
                    var key = new Tuple<Directions, Metrics>(dir, met);
                    getRLFImage(pixels, i);
                    this.rlfImages[key] = PixelsToBitmap(pixels, this.outputImageWidth);
                    i++;
                }
            }

            // release C resources at once, so there will be no conflicts
            RLFClient.destroy();
        }

        #endregion

        #region Private methods

        private int[] BitmapToPixels(Bitmap image)
        {
            int[] pixels = new int[image.Width * image.Height];
            int i = 0;

            for (int y = 0; y < image.Height; y++)
            {
                for (int x = 0; x < image.Width; x++)
                {
                    Color col = image.GetPixel(x, y);
                    pixels[i] = (int)((double)col.R * 0.2126 + (double)col.G * 0.7152 + (double)col.B * 0.0722);
                    i++;
                }
            }

            return pixels;
        }

        private Bitmap PixelsToBitmap(int[] pixels, int imageWidth)
        {
            Bitmap image = new Bitmap(imageWidth, pixels.Length / imageWidth);

            int i = 0;

            for (int y = 0; y < image.Height; y++)
            {
                for (int x = 0; x < image.Width; x++) 
                {
                    image.SetPixel(x, y, Color.FromArgb( pixels[i], pixels[i], pixels[i]));
                    i++;
                }
            }

            return image;
        }

        #endregion
    }
}
